package com.jprocms.module.cms.controller.directive;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.jpro.framework.common.pojo.PageResult;
import com.jpro.framework.common.util.string.StrUtils;
import com.jprocms.module.cms.convert.content.ContentConvert;
import com.jprocms.module.cms.dal.dataobject.channel.ChannelDO;
import com.jprocms.module.cms.es.dto.EsFrontSearchDto;
import com.jprocms.module.cms.es.dto.FrontSearchDto;
import com.jprocms.module.cms.es.service.ContentEsService;
import com.jprocms.module.cms.es.vo.EsContentVO;
import com.jprocms.module.cms.service.channel.ChannelService;
import com.jprocms.module.cms.service.sysword.SysWordService;
import com.jprocms.module.cms.util.Freemarkers;
import com.jprocms.module.cms.util.FrontContextUtil;
import com.jprocms.module.cms.web.Directives;
import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.Nullable;

import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.jprocms.module.cms.constant.CmsSysConstants.COLL_SPT;
import static com.jprocms.module.cms.web.Directives.*;


/**
 * 内容搜索列表 自定义标签
 *
 * @author jprocms
 */
public class ContentSearchPageDirective implements TemplateDirectiveModel {
    /**
     * 站点ID。
     */
    public static final String SITE_ID = "siteId";
    /**
     * 栏目路径。
     */
    public static final String CHANNEL_PATH = "channelPath";
    /**
     * 栏目ID。
     */
    public static final String CHANNEL_ID = "channelId";
    /**
     * 开始发布日期。
     */
    public static final String BEGIN_RELEASE_DATE = "releaseTimeStart";
    /**
     * 结束发布日期。
     */
    public static final String END_RELEASE_DATE = "releaseTimeEnd";
    /**
     * 标题
     */
    public static final String TITLE = "title";
    /**
     * 标题或正文。ES搜索使用
     */
    public static final String Q = "q";
    /**
     * 搜索关键词匹配规则，1:匹配任意关键词 2：匹配所有关键词 3：所有关键词都不匹配 默认 1
     */
    public static final String Q_MATCHTYPE = "qMatchType";
    /**
     * 搜索关键词匹配项，1:标题匹配 2：内容文本匹配 3：标题匹配或者内容文本匹配 默认 3
     */
    public static final String Q_MATCH_POS = "qMathPos";
    /**
     * 正文(只在es支持搜索的时候使用)
     */
    public static final String TEXT = "text";
    /**
     * 是否包含子栏目的内容。布尔型(Boolean)。默认：true。
     */
    public static final String INCLUDE_CHILD_CHANNEL = "includeChildChannel";
    /**
     * 置顶级别
     */
    public static final String TOP_LEVEL = "topLevel";
    /**
     * 标记属性id 来自数据字典 多个用英文逗号分隔，
     */
    public static final String DICT_MARK = "dictMark";
    /**
     * 区块ID 来自数据字典 多个用英文逗号分隔，
     */
    public static final String DICT_BLOCK = "dictBlock";
    /**
     * 最大分页数量
     */
    public static final String PAGE_MAX = "pageMax";
    /**
     * 发布时间查询范围 1：当天内 2：当周内：3：当月内 4：当年内 5：指定发布时间范围
     */
    public static final String TIME_STAGE = "timeStage";


    private Collection<Long> getChannelIds(Map<String, ?> params, @Nullable Long siteId, boolean includeChild) {
        String channelIds = getString(params, CHANNEL_ID);
        String channelPaths = getString(params, CHANNEL_PATH);
        return combinChannelIds(channelIds, channelPaths, siteId, includeChild);
    }

    /**
     * 合并栏目id和栏目路径为栏目id  查询所有叶子节点栏目 集合
     *
     * @param channelIdStr
     * @param channelPaths
     * @return
     */
    private Collection<Long> combinChannelIds(String channelIdStr, String channelPaths, Long siteId, boolean includeChild) {
        List<Long> channelIds = new ArrayList<>();
        if (StringUtils.isNotBlank(channelIdStr)) {
            channelIds.addAll(StrUtils.splitToLong(channelIdStr));
        }
        if (StringUtils.isNotBlank(channelPaths)) {
            channelIds.addAll(findChannelIdsByPath(channelPaths, siteId));
        }
        if (CollectionUtils.isEmpty(channelIds)) {
            return channelIds;
        }
        if (includeChild) {
            return com.jpro.framework.common.util.collection.CollectionUtils.convertList(channelService.selectLeafChannels(channelIds), ChannelDO::getId);
        } else {
            return channelIds;
        }
    }

    /**
     * 查询栏id 根据栏目路径
     *
     * @param channelPaths 栏目路径
     * @return
     */
    private List<Long> findChannelIdsByPath(String channelPaths, Long siteId) {
        List<String> paths = StrUtils.splitToString(channelPaths);
        List<ChannelDO> channels = channelService.selectByChannelPath(paths, siteId);
        return com.jpro.framework.common.util.collection.CollectionUtils.convertList(channels, ChannelDO::getId);
    }

    protected void doExecute(Environment env, Map<String, TemplateModel> params, TemplateModel[] loopVars,
                             TemplateDirectiveBody body, boolean isPage) throws TemplateException, IOException {
        Freemarkers.requireLoopVars(loopVars);
        Freemarkers.requireBody(body);
        FrontSearchDto frontSearchDto = new FrontSearchDto();
        Long defSiteId = FrontContextUtil.getSiteId(env);
        Long siteId = getLong(params, SITE_ID);
        // 不获取所有站点，则给默认站点ID
        if (siteId == null) {
            siteId = defSiteId;
        }
        frontSearchDto.setSiteId(siteId);
        Boolean includeChildChannel = getBoolean(params, INCLUDE_CHILD_CHANNEL, true);
        Collection<Long> channelIds = getChannelIds(params, siteId, includeChildChannel);
        if (CollUtil.isNotEmpty(channelIds)) {
            frontSearchDto.setChannelIdStrs(StrUtil.join(COLL_SPT, channelIds));
        }
        final OffsetDateTime releaseStartDateTime = getOffsetDateTime(params, BEGIN_RELEASE_DATE);
        if (releaseStartDateTime != null) {
            final Date releaseStartDate = Date.from(releaseStartDateTime.toInstant());
            frontSearchDto.setReleaseTimeStart(releaseStartDate);
        }
        final OffsetDateTime releaseEndDateTime = getOffsetDateTime(params, END_RELEASE_DATE);
        if (releaseEndDateTime != null) {
            final Date releaseEndDate = Date.from(releaseEndDateTime.toInstant());
            frontSearchDto.setReleaseTimeEnd(releaseEndDate);
        }
        Optional.ofNullable(Directives.getString(params, Q)).ifPresent(frontSearchDto::setQ);
        Optional.ofNullable(Directives.getInteger(params, Q_MATCHTYPE)).ifPresent(frontSearchDto::setQMatchType);
        Optional.ofNullable(Directives.getInteger(params, Q_MATCH_POS)).ifPresent(frontSearchDto::setQMathPos);
        Optional.ofNullable(getBoolean(params, TOP_LEVEL)).ifPresent(frontSearchDto::setIsTop);
        Optional.ofNullable(getString(params, DICT_BLOCK)).ifPresent(frontSearchDto::setDictBlockStrs);
        Optional.ofNullable(getString(params, DICT_MARK)).ifPresent(frontSearchDto::setDictMarkStrs);
        Optional.ofNullable(getInteger(params, TIME_STAGE)).ifPresent(frontSearchDto::setTimeStage);
        Optional.ofNullable(getInteger(params, ORDER_BY)).ifPresent(frontSearchDto::setOrderBy);
        //自定义查询条件
        Map<String, String> customsQueryMap = getCustomsQueryMap(params);
        if (CollUtil.isNotEmpty(customsQueryMap)) {
            Map<String, Object> customsQueryObjMap = customsQueryMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()));
            frontSearchDto.setCustoms(customsQueryObjMap);
        }

        final String q = frontSearchDto.getQ();
        //搜索词记录
        wordService.addSearchCount(Arrays.asList(q), 1L, siteId);
        if (isPage) {
            frontSearchDto.setPageSize(Long.parseLong(Directives.getPageSize(params, env).toString()));
            Integer maxPage = getInteger(params, PAGE_MAX);
            Integer pageNo = getPage(params, env);
            if (maxPage != null && pageNo > maxPage) {
                pageNo = maxPage;
            }
            frontSearchDto.setPageNo(Long.parseLong(pageNo.toString()));
            EsFrontSearchDto searchDto = ContentConvert.INSTANCE.convert2EsQuertDto(frontSearchDto);
            PageResult<EsContentVO> contentPageFront = contentEsService.getContentPageForFront(searchDto);
            Directives.setTotalPages(contentPageFront.getTotalPages());
            loopVars[0] = env.getObjectWrapper().wrap(contentPageFront);
        } else {
            frontSearchDto.setOffset(Directives.getOffset(params));
            frontSearchDto.setPageSize(Directives.getCount(params).longValue());
            EsFrontSearchDto searchDto = ContentConvert.INSTANCE.convert2EsQuertDto(frontSearchDto);
            PageResult<EsContentVO> contentPageFront = contentEsService.getContentPageForFront(searchDto);
            Directives.setTotalPages(contentPageFront.getTotalPages());
            loopVars[0] = env.getObjectWrapper().wrap(contentPageFront);
        }
        body.render(env.getOut());
    }

    @SuppressWarnings("unchecked")
    @Override
    public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
            throws TemplateException, IOException {
        doExecute(env, params, loopVars, body, true);
    }

    private final ChannelService channelService;
    private ContentEsService contentEsService;

    private SysWordService wordService;

    public ContentSearchPageDirective(ChannelService channelService, ContentEsService contentEsService, SysWordService wordService) {
        this.channelService = channelService;
        this.contentEsService = contentEsService;
        this.wordService = wordService;
    }
}
